package app.framework.front.handler;

import app.framework.constant.FrontConstants;
import app.framework.freemarker.util.RenderHtmlUtil;
import app.framework.freemarker.util.RenderJsUtil;
import app.framework.front.util.FrontScanUtil;
import app.framework.front.util.PageConfig;
import app.framework.front.util.WebUtil;
import cn.hutool.core.net.URLEncodeUtil;
import cn.hutool.core.util.ReflectUtil;
import freemarker.template.Configuration;
import freemarker.template.TemplateNotFoundException;
import jasmine.framework.common.util.CheckUtil;
import jasmine.framework.common.util.ErrorUtil;
import jasmine.framework.common.util.I18nUtil;
import jasmine.framework.common.util.StringUtil;
import jasmine.framework.i18n.I18nConstants;
import jasmine.framework.web.util.routing.Route;
import jasmine.framework.web.util.routing.Router;
import org.springframework.context.MessageSource;
import org.springframework.context.support.AbstractMessageSource;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Properties;

/**
 * @author mh.z
 */
public class DefaultFrontHandler implements FrontHandler {
    /** FreeMarker模板引擎配置 */
    private Configuration freeMarkerConfig;
    /** 多语言资源 */
    private MessageSource messageSource;
    /** 页面配置的路径 */
    private String pageConfigLocationPattern;
    /** 多语言文件的路径 */
    private String i18nResourceLocationPattern;
    /** 脚本请求的前缀 */
    private String scriptUrlPrefix;

    /** 默认的多语言 */
    private Properties originalCommonMessages;
    /** 页面路由器 */
    private Router pageRouter;
    /** 缓存 */
    private FrontCache frontCache;

    private static final String SCRIPT_URL_SUFFIX = ".js";
    private static final String PATH_PARAMETER_START_SYMBOL = "{";
    private static final String PATH_PARAMETER_END_SYMBOL = "}";

    protected DefaultFrontHandler(Configuration freeMarkerConfig, MessageSource messageSource,
                                  String pageConfigLocationPattern, String i18nResourceLocationPattern,
                                  String scriptUrlPrefix) {
        this.freeMarkerConfig = freeMarkerConfig;
        this.messageSource = messageSource;
        this.pageConfigLocationPattern = pageConfigLocationPattern;
        this.i18nResourceLocationPattern = i18nResourceLocationPattern;
        this.scriptUrlPrefix = scriptUrlPrefix;
        this.originalCommonMessages = null;
        this.pageRouter = new Router(PATH_PARAMETER_START_SYMBOL, PATH_PARAMETER_END_SYMBOL);
        this.frontCache = new NullFrontCache();
    }

    protected void setPageRouter(Router pageRouter) {
        this.pageRouter = pageRouter;
    }

    protected void setFrontCache(FrontCache frontCache) {
        this.frontCache = frontCache;
    }

    /**
     * 创建处理器
     *
     * @param config
     * @param cache
     * @return
     */
    public static DefaultFrontHandler create(FrontConfig config, FrontCache cache) {
        CheckUtil.notNull(config, "config null");
        Configuration freeMarkerConfig = CheckUtil.notNull(config.getFreeMarkerConfig(),
                "config.freeMarkerConfig null");
        MessageSource messageSource = CheckUtil.notNull(config.getMessageSource(),
                "config.messageSource null");
        String pageConfigLocationPattern = CheckUtil.notNull(config.getPageConfigLocationPattern(),
                "config.pageConfigLocationPattern null");
        String i18nResourceLocationPattern = CheckUtil.notNull(config.getI18nResourceLocationPattern(),
                "config.i18nResourceLocationPattern null");
        String scriptUrlPrefix = CheckUtil.notNull(config.getScriptUrlPrefix(),
                "config.scriptUrlPrefix null");

        DefaultFrontHandler handler = new DefaultFrontHandler(freeMarkerConfig, messageSource,
                pageConfigLocationPattern, i18nResourceLocationPattern, scriptUrlPrefix);
        // 刷新上下文
        handler.refreshContext();

        if (cache != null) {
            handler.setFrontCache(cache);
        }

        return handler;
    }

    /**
     * 处理请求
     *
     * @param request
     * @param response
     * @return
     */
    @Override
    public boolean handleRequest(HttpServletRequest request, HttpServletResponse response) {
        String originalUrl = WebUtil.getRequestUriWithoutContextPath(request);
        boolean returnResult;

        if (originalUrl.endsWith(SCRIPT_URL_SUFFIX)) {
            // 处理脚本
            returnResult = doHandlerJs(request, response);
        } else {
            // 处理页面
            returnResult = doHandlerHtml(request, response);
        }

        return returnResult;
    }

    /**
     * 处理脚本
     *
     * @param request
     * @param response
     * @return
     */
    protected boolean doHandlerJs(HttpServletRequest request, HttpServletResponse response) {
        String originalUrl = WebUtil.getRequestUriWithoutContextPath(request);
        boolean returnResult = true;

        if (!originalUrl.startsWith(scriptUrlPrefix)) {
            return false;
        }

        // 先尝试从缓存中获取
        String cacheKey = originalUrl + "." + I18nUtil.getLanguage();
        String jsContent = frontCache.getContent(cacheKey);

        if (jsContent == null) {
            StringWriter writer = new StringWriter();
            String jsPath = originalUrl.substring(scriptUrlPrefix.length());

            try {
                RenderJsUtil.renderContent(freeMarkerConfig, writer, jsPath);
                jsContent = writer.toString();
                // 添加到缓存
                frontCache.putContent(cacheKey, jsContent);
            } catch (Exception e) {
                if (e instanceof TemplateNotFoundException) {
                    return false;
                } else {
                    throw ErrorUtil.sneakyError(e);
                }
            }
        }

        // 设置响应体信息
        WebUtil.setResponseInfo(response, HttpServletResponse.SC_OK,
                FrontConstants.RESPONSE_CONTENT_TYPE_SCRIPT);
        // 输出脚本内容
        WebUtil.writeAndFlush(response, jsContent);

        return returnResult;
    }

    /**
     * 处理页面
     *
     * @param request
     * @param response
     * @return
     */
    protected boolean doHandlerHtml(HttpServletRequest request, HttpServletResponse response) {
        String originalUrl = WebUtil.getRequestUriWithoutContextPath(request);
        boolean returnResult = true;

        Route route = pageRouter.getRoute(originalUrl);
        if (route == null) {
            return false;
        }

        PageConfig pageConfig = (PageConfig) route.getBindingValue();
        // 先尝试从缓存中获取
        String cacheKey = originalUrl + "." + I18nUtil.getLanguage();
        String pageContent = frontCache.getContent(cacheKey);

        if (pageContent == null) {
            StringWriter writer = new StringWriter();

            try {
                RenderHtmlUtil.renderContent(freeMarkerConfig, writer, pageConfig.getFilePath());
                pageContent = writer.toString();
                // 添加到缓存
                frontCache.putContent(cacheKey, pageContent);
            } catch (Exception e) {
                if (e instanceof TemplateNotFoundException) {
                    return false;
                } else {
                    throw ErrorUtil.sneakyError(e);
                }
            }
        }

        String pageUrl = originalUrl;
        String queryString = request.getQueryString();

        if (StringUtil.isNotEmpty(queryString)) {
            pageUrl = pageUrl + "?" + queryString;
        }

        String pageName = pageConfig.getPageName();
        if (pageName != null && pageName.startsWith(I18nConstants.I18N_MESSAGE_KEY_PREFIX)) {
            pageName = I18nUtil.getMessageAllowNone(pageName);
            pageName = URLEncodeUtil.encode(pageName, StandardCharsets.UTF_8);
        }

        int responseStatus = response.getStatus();
        // 设置响应体信息
        WebUtil.setResponseInfo(response, responseStatus,
                FrontConstants.RESPONSE_CONTENT_TYPE_HTML);
        // 返回页面代码/页面URL/URL模式
        response.setHeader(FrontConstants.RESPONSE_HEADER_PAGE_CODE, pageConfig.getPageCode());
        response.setHeader(FrontConstants.RESPONSE_HEADER_PAGE_NAME, pageName);
        response.setHeader(FrontConstants.RESPONSE_HEADER_PAGE_URL, pageUrl);
        response.setHeader(FrontConstants.RESPONSE_HEADER_URL_PATTERN, pageConfig.getUrlPattern());

        // 输出页面内容
        WebUtil.writeAndFlush(response, pageContent);

        return returnResult;
    }

    /**
     * 刷新上下文
     */
    @Override
    public synchronized void refreshContext() {
        // 扫描页面配置
        doScanPageConfigs();

        // 扫描多语言资源
        doScanI18nResources();
    }

    @Override
    public String getUrlPattern(String url) {
        Route route = pageRouter.getRoute(url);

        if (route != null) {
            PageConfig pageConfig = (PageConfig) route.getBindingValue();

            return pageConfig.getUrlPattern();
        }

        return null;
    }

    /**
     * 扫描页面配置
     */
    protected void doScanPageConfigs() {
        pageRouter = new Router(PATH_PARAMETER_START_SYMBOL, PATH_PARAMETER_END_SYMBOL);
        // 扫描页面配置
        List<PageConfig> pageConfigList = FrontScanUtil.scanPageConfigs(pageConfigLocationPattern);

        for (PageConfig pageConfig : pageConfigList) {
            String urlPattern = pageConfig.getUrlPattern();
            // 添加路由
            pageRouter.addRoute(urlPattern, pageConfig);
        }
    }

    /**
     * 扫描多语言资源
     */
    protected void doScanI18nResources() {
        // 扫描多语言资源
        Properties scannedMessages = FrontScanUtil.scanI18nResources(i18nResourceLocationPattern);
        // 获取默认的多语言
        AbstractMessageSource abstractMessageSource = (AbstractMessageSource) messageSource;
        Properties commonMessages = (Properties) ReflectUtil
                .getFieldValue(abstractMessageSource, "commonMessages");

        if (commonMessages == null) {
            commonMessages = new Properties();
        }

        if (originalCommonMessages == null) {
            originalCommonMessages = commonMessages;
        }

        Properties newCommonMessages = new Properties(originalCommonMessages);
        newCommonMessages.putAll(scannedMessages);
        // 设置默认的多语言
        abstractMessageSource.setCommonMessages(newCommonMessages);
    }

}
